/*M///////////////////////////////////////////////////////////////////////////////////////
//
//  IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING.
//
//  By downloading, copying, installing or using the software you agree to this license.
//  If you do not agree to this license, do not download, install,
//  copy or use the software.
//
//
//                           License Agreement
//                For Open Source Computer Vision Library
//
// Copyright (C) 2000-2008, Intel Corporation, all rights reserved.
// Copyright (C) 2009, Willow Garage Inc., all rights reserved.
// Third party copyrights are property of their respective owners.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//   * Redistribution's of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//
//   * Redistribution's in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//
//   * The name of the copyright holders may not be used to endorse or promote products
//     derived from this software without specific prior written permission.
//
// This software is provided by the copyright holders and contributors "as is" and
// any express or implied warranties, including, but not limited to, the implied
// warranties of merchantability and fitness for a particular purpose are disclaimed.
// In no event shall the Intel Corporation or contributors be liable for any direct,
// indirect, incidental, special, exemplary, or consequential damages
// (including, but not limited to, procurement of substitute goods or services;
// loss of use, data, or profits; or business interruption) however caused
// and on any theory of liability, whether in contract, strict liability,
// or tort (including negligence or otherwise) arising in any way out of
// the use of this software, even if advised of the possibility of such damage.
//
//M*/

#include "precomp.hpp"

#ifdef HAVE_PNG

/****************************************************************************************\
    This part of the file implements PNG codec on base of libpng library,
    in particular, this code is based on example.c from libpng
    (see otherlibs/_graphics/readme.txt for copyright notice)
    and png2bmp sample from libpng distribution (Copyright (C) 1999-2001 MIYASAKA Masaru)
\****************************************************************************************/

#ifdef HAVE_LIBPNG_PNG_H
#include <libpng/png.h>
#else
#include <png.h>
#endif
#include "grfmt_png.hpp"

namespace cv {

/////////////////////// PngDecoder ///////////////////

PngDecoder::PngDecoder() {
	m_signature = "\x89\x50\x4e\x47\xd\xa\x1a\xa";
	m_color_type = 0;
	m_png_ptr = 0;
	m_info_ptr = m_end_info = 0;
	m_f = 0;
	m_buf_supported = true;
	m_buf_pos = 0;
}


PngDecoder::~PngDecoder() {
	close();
}

ImageDecoder PngDecoder::newDecoder() const {
	return new PngDecoder;
}

void  PngDecoder::close() {
	if ( m_f ) {
		fclose( m_f );
		m_f = 0;
	}

	if ( m_png_ptr ) {
		png_structp png_ptr = (png_structp)m_png_ptr;
		png_infop info_ptr = (png_infop)m_info_ptr;
		png_infop end_info = (png_infop)m_end_info;
		png_destroy_read_struct( &png_ptr, &info_ptr, &end_info );
		m_png_ptr = m_info_ptr = m_end_info = 0;
	}
}


void  PngDecoder::readDataFromBuf( void* _png_ptr, uchar* dst, size_t size ) {
	png_structp png_ptr = (png_structp)_png_ptr;
	PngDecoder* decoder = (PngDecoder*)(png_ptr->io_ptr);
	CV_Assert( decoder );
	const Mat& buf = decoder->m_buf;
	if ( decoder->m_buf_pos + size > buf.cols * buf.rows * buf.elemSize() ) {
		png_error(png_ptr, "PNG input buffer is incomplete");
		return;
	}
	memcpy( dst, &decoder->m_buf.data[decoder->m_buf_pos], size );
	decoder->m_buf_pos += size;
}

bool  PngDecoder::readHeader() {
	bool result = false;
	close();

	png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );

	if ( png_ptr ) {
		png_infop info_ptr = png_create_info_struct( png_ptr );
		png_infop end_info = png_create_info_struct( png_ptr );

		m_png_ptr = png_ptr;
		m_info_ptr = info_ptr;
		m_end_info = end_info;
		m_buf_pos = 0;

		if ( info_ptr && end_info ) {
			if ( setjmp( png_ptr->jmpbuf ) == 0 ) {
				if ( !m_buf.empty() ) {
					png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf );
				} else {
					m_f = fopen( m_filename.c_str(), "rb" );
					if ( m_f ) {
						png_init_io( png_ptr, m_f );
					}
				}

				if ( !m_buf.empty() || m_f ) {
					png_uint_32 width, height;
					int bit_depth, color_type;

					png_read_info( png_ptr, info_ptr );

					png_get_IHDR( png_ptr, info_ptr, &width, &height,
								  &bit_depth, &color_type, 0, 0, 0 );

					m_width = (int)width;
					m_height = (int)height;
					m_color_type = color_type;
					m_bit_depth = bit_depth;

					if ( bit_depth <= 8 || bit_depth == 16 ) {
						m_type = color_type == PNG_COLOR_TYPE_RGB ||
								 color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
								 color_type == PNG_COLOR_TYPE_PALETTE ? CV_8UC3 : CV_8UC1;
						if ( bit_depth == 16 ) {
							m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
						}
						result = true;
					}
				}
			}
		}
	}

	if ( !result ) {
		close();
	}

	return result;
}


bool  PngDecoder::readData( Mat& img ) {
	bool result = false;
	AutoBuffer<uchar*> _buffer(m_height);
	uchar** buffer = _buffer;
	int color = img.channels() > 1;
	uchar* data = img.data;
	int step = img.step;

	if ( m_png_ptr && m_info_ptr && m_end_info && m_width && m_height ) {
		png_structp png_ptr = (png_structp)m_png_ptr;
		png_infop info_ptr = (png_infop)m_info_ptr;
		png_infop end_info = (png_infop)m_end_info;

		if ( setjmp(png_ptr->jmpbuf) == 0 ) {
			int y;

			if ( img.depth() == CV_8U && m_bit_depth == 16 ) {
				png_set_strip_16( png_ptr );
			} else if ( !isBigEndian() ) {
				png_set_swap( png_ptr );
			}

			/* observation: png_read_image() writes 400 bytes beyond
			 * end of data when reading a 400x118 color png
			 * "mpplus_sand.png".  OpenCV crashes even with demo
			 * programs.  Looking at the loaded image I'd say we get 4
			 * bytes per pixel instead of 3 bytes per pixel.  Test
			 * indicate that it is a good idea to always ask for
			 * stripping alpha..  18.11.2004 Axel Walthelm
			 */
			png_set_strip_alpha( png_ptr );

			if ( m_color_type == PNG_COLOR_TYPE_PALETTE ) {
				png_set_palette_to_rgb( png_ptr );
			}

			if ( m_color_type == PNG_COLOR_TYPE_GRAY && m_bit_depth < 8 ) {
				png_set_gray_1_2_4_to_8( png_ptr );
			}

			if ( CV_MAT_CN(m_type) > 1 && color ) {
				png_set_bgr( png_ptr );    // convert RGB to BGR
			} else if ( color ) {
				png_set_gray_to_rgb( png_ptr );    // Gray->RGB
			} else {
				png_set_rgb_to_gray( png_ptr, 1, -1, -1 );    // RGB->Gray
			}

			png_read_update_info( png_ptr, info_ptr );

			for ( y = 0; y < m_height; y++ ) {
				buffer[y] = data + y * step;
			}

			png_read_image( png_ptr, buffer );
			png_read_end( png_ptr, end_info );

			result = true;
		}
	}

	close();
	return result;
}


/////////////////////// PngEncoder ///////////////////


PngEncoder::PngEncoder() {
	m_description = "Portable Network Graphics files (*.png)";
	m_buf_supported = true;
}


PngEncoder::~PngEncoder() {
}


bool  PngEncoder::isFormatSupported( int depth ) const {
	return depth == CV_8U || depth == CV_16U;
}

ImageEncoder PngEncoder::newEncoder() const {
	return new PngEncoder;
}


void PngEncoder::writeDataToBuf(void* _png_ptr, uchar* src, size_t size) {
	if ( size == 0 ) {
		return;
	}
	png_structp png_ptr = (png_structp)_png_ptr;
	PngEncoder* encoder = (PngEncoder*)(png_ptr->io_ptr);
	CV_Assert( encoder && encoder->m_buf );
	size_t cursz = encoder->m_buf->size();
	encoder->m_buf->resize(cursz + size);
	memcpy( &(*encoder->m_buf)[cursz], src, size );
}


void PngEncoder::flushBuf(void*) {
}

bool  PngEncoder::write( const Mat& img, const vector<int>& params ) {
	int compression_level = 0;

	for ( size_t i = 0; i < params.size(); i += 2 ) {
		if ( params[i] == CV_IMWRITE_PNG_COMPRESSION ) {
			compression_level = params[i+1];
			compression_level = MIN(MAX(compression_level, 0), MAX_MEM_LEVEL);
		}
	}

	png_structp png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
	png_infop info_ptr = 0;
	FILE* f = 0;
	int y, width = img.cols, height = img.rows;
	int depth = img.depth(), channels = img.channels();
	bool result = false;
	AutoBuffer<uchar*> buffer;

	if ( depth != CV_8U && depth != CV_16U ) {
		return false;
	}

	if ( png_ptr ) {
		info_ptr = png_create_info_struct( png_ptr );

		if ( info_ptr ) {
			if ( setjmp( png_ptr->jmpbuf ) == 0 ) {
				if ( m_buf ) {
					png_set_write_fn(png_ptr, this,
									 (png_rw_ptr)writeDataToBuf, (png_flush_ptr)flushBuf);
				} else {
					f = fopen( m_filename.c_str(), "wb" );
					if ( f ) {
						png_init_io( png_ptr, f );
					}
				}

				if ( m_buf || f ) {
					if ( compression_level > 0 ) {
						png_set_compression_mem_level( png_ptr, compression_level );
					} else {
						// tune parameters for speed
						// (see http://wiki.linuxquestions.org/wiki/Libpng)
						png_set_filter(png_ptr, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB);
						png_set_compression_level(png_ptr, Z_BEST_SPEED);
					}
					png_set_compression_strategy(png_ptr, Z_HUFFMAN_ONLY);

					png_set_IHDR( png_ptr, info_ptr, width, height, depth == CV_8U ? 8 : 16,
								  channels == 1 ? PNG_COLOR_TYPE_GRAY :
								  channels == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA,
								  PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
								  PNG_FILTER_TYPE_DEFAULT );

					png_write_info( png_ptr, info_ptr );

					png_set_bgr( png_ptr );
					if ( !isBigEndian() ) {
						png_set_swap( png_ptr );
					}

					buffer.allocate(height);
					for ( y = 0; y < height; y++ ) {
						buffer[y] = img.data + y * img.step;
					}

					png_write_image( png_ptr, buffer );
					png_write_end( png_ptr, info_ptr );

					result = true;
				}
			}
		}
	}

	png_destroy_write_struct( &png_ptr, &info_ptr );
	if (f) { fclose( f ); }

	return result;
}

}

#endif

/* End of file. */
